package com.github.thorqin.toolkit.web.utility;

import com.github.thorqin.toolkit.store.FileStoreService;
import com.github.thorqin.toolkit.utility.MimeUtils;
import com.github.thorqin.toolkit.utility.Serializer;
import com.github.thorqin.toolkit.web.WebApplication;
import com.google.common.base.Strings;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;

/**
 * Created by nuo.qin on 12/25/2014.
 */
public final class ServletUtils {
	public final static long DEFAULT_MAX_UPLOAD_SIZE = 1024 * 1024 * 50;
	public final static String DEFAULT_UPLOAD_SUFFIX = "jpg|jpeg|png|gif";
	public final static int COMPRESSION_MIN_SIZE = 2048;

    private static Logger logger = Logger.getLogger(ServletUtils.class.getName());

    public static class DownloadOptions {
		public String fileName = null;
		public String mimeType = null;
		public Long fileLength = null;
		public String eTag = null;
		public Long lastModified = null;
		public boolean inline = false;
		public String cacheControl = "private";
		public long maxAge = 86400;
		public boolean supportRange = false;
	}

	public static class UploadOptions {
    	public String allowSuffix = DEFAULT_UPLOAD_SUFFIX;
    	public String fileNamePattern = null;
    	public long maxUploadFileSize = DEFAULT_MAX_UPLOAD_SIZE;
	}

    public static void setCrossSiteHeaders(HttpServletRequest request, HttpServletResponse response) {
        String origin = request.getHeader("Origin");
        if (origin == null)
            origin = "*";
        response.setHeader("Access-Control-Allow-Origin", origin);
        if (origin.equals("*"))
            response.addHeader("Access-Control-Allow-Origin", "null");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("P3P", "CP=CAO PSA OUR");
        response.setHeader("Access-Control-Allow-Methods",
                "GET,POST,PUT,DELETE,HEAD,OPTIONS");
        response.setHeader("Access-Control-Allow-Headers",
                "Content-Type," +
					"Keep-Alive," +
					"User-Agent," +
					"X-Requested-With," +
					"X-App-Version," +
					"X-Cordova-Version," +
					"X-Model," +
					"X-OS," +
					"X-OS-Version," +
					"X-UUID," +
					"If-Modified-Since," +
					"Cache-Control");
    }

    public static String getURL(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder(request.getRequestURL());
        String queryString = request.getQueryString();
        if (queryString != null)
            sb.append("?").append(queryString);
        return sb.toString();
    }

    public static boolean supportGZipCompression(HttpServletRequest request) {
        Enumeration<String> headers = request.getHeaders("Accept-Encoding");
        while (headers.hasMoreElements()) {
            String[] value = headers.nextElement().split(",");
            for (String v : value) {
                if (v.trim().toLowerCase().equals("gzip"))
                    return true;
            }
        }
        return false;
    }

    public static String readHttpBody(HttpServletRequest request) {
        try {
            InputStream is = request.getInputStream();
            if (is != null) {
                Writer writer = new StringWriter();
                char[] buffer = new char[1024];
                try {
                    String encoding = request.getCharacterEncoding();
                    if (encoding == null) {
                        encoding = "UTF-8";
                    }
                    Reader reader = new BufferedReader(
                            new InputStreamReader(is, encoding));
                    int n;
                    while ((n = reader.read(buffer)) != -1) {
                        writer.write(buffer, 0, n);
                    }
                } catch (IOException ex) {
                    logger.log(Level.WARNING,
                            "Read http body failed: ", ex);
                }
                return writer.toString();
            } else {
                return "";
            }
        } catch (IOException e) {
            return "";
        }
    }

    public static void sendText(HttpServletResponse response, Integer status, String message, boolean compress) {
        response.setStatus(status);
        sendText(response, message, compress);
    }

    public static void sendText(HttpServletResponse response, Integer status, String message) {
        response.setStatus(status);
        sendText(response, message, false);
    }

    public static void sendText(HttpServletResponse response, Integer status, String message, String contentType, boolean compress) {
        response.setStatus(status);
        sendText(response, message, contentType, compress);
    }

    public static void sendText(HttpServletResponse response, Integer status, String message, String contentType) {
        response.setStatus(status);
        sendText(response, message, contentType, false);
    }

    public static void sendText(HttpServletResponse response, String message, String contentType) {
        sendText(response, message, contentType, false);
    }

    public static void sendText(HttpServletResponse response, String message, String contentType, boolean compress) {
        try {
			response.setContentType(contentType);
			response.setCharacterEncoding("utf-8");
			response.setHeader("Cache-Control", "max-age=0");
			response.setDateHeader("Expires", 0);
			if (message == null)
				return;
			compress = compress && message.length() > ServletUtils.COMPRESSION_MIN_SIZE;
			if (compress) {
				response.addHeader("Content-Encoding", "gzip");
				response.addHeader("Transfer-Encoding", "chunked");
				try (GZIPOutputStream gzipStream = new GZIPOutputStream(response.getOutputStream(), true)) {
					try (OutputStreamWriter writer = new OutputStreamWriter(gzipStream, "utf-8")) {
						writer.write(message);
						writer.flush();
					}
					gzipStream.finish();
					gzipStream.flush();
				}
			} else {
				try (Writer w = response.getWriter()) {
					w.write(message);
				}
			}
		} catch (Exception ex) {
			logger.log(Level.SEVERE, "Send message to client failed!", ex);
		}
    }

    public static void sendText(HttpServletResponse response, String message, boolean compress) {
        sendText(response, message, "text/plain", compress);
    }

    public static void sendText(HttpServletResponse response, String message) {
        sendText(response, message, "text/plain", false);
    }

    public static void sendHtml(HttpServletResponse response, String html, boolean compress) {
        sendText(response, html, "text/html", compress);
    }

    public static void sendHtml(HttpServletResponse response, String html) {
        sendText(response, html, "text/html", false);
    }

    public static void sendHtml(HttpServletResponse response, Integer status, String html, boolean compress) {
        sendText(response, status, html, "text/html", compress);
    }

    public static void sendHtml(HttpServletResponse response, Integer status, String html) {
        sendText(response, status, html, "text/html", false);
    }

    public static void sendJsonString(HttpServletResponse response, Integer status, String jsonString, boolean compress) {
        response.setStatus(status);
        sendJsonString(response, jsonString, compress);
    }

    public static void sendJsonString(HttpServletResponse response, Integer status, String jsonString) {
        response.setStatus(status);
        sendJsonString(response, jsonString, false);
    }

    public static void sendJsonString(HttpServletResponse response, String jsonString, boolean compress) {
        sendText(response, jsonString, "application/json", compress);
    }

    public static void sendJsonString(HttpServletResponse response, String jsonString) {
        sendText(response, jsonString, "application/json", false);
    }

    public static void send(HttpServletResponse response, Integer status) {
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-store");
        response.setDateHeader("Expires", 0);
        response.setStatus(status);
    }

    public static void sendDownloadHeader(
    	HttpServletRequest req,
		HttpServletResponse resp,
		DownloadOptions options) {
    	if (options == null) {
			options = new DownloadOptions();
		}
		boolean forceDisposition = false;
        try {
            if (options.fileName != null) {
				options.fileName = URLEncoder.encode(options.fileName, "utf-8").replace("+", "%20");
				forceDisposition = true;
            } else {
				options.fileName = "download.dat";
            }
        } catch (UnsupportedEncodingException e1) {
			options.fileName = "download.dat";
        }
        if (options.mimeType == null) {
            String extName = "";
            if (options.fileName.lastIndexOf(".") > 0)
                extName = options.fileName.substring(options.fileName.lastIndexOf(".") + 1);
			options.mimeType = MimeUtils.getFileMime(extName);
        }
		resp.setContentType(options.mimeType);
		if (options.eTag != null || options.lastModified != null) {
			resp.setHeader("Cache-Control",  options.cacheControl + ", max-age=" + options.maxAge + ", must-revalidate");
			if (options.eTag != null) {
				resp.setHeader("ETag", options.eTag);
			}
			if (options.lastModified != null) {
				resp.setDateHeader("Last-Modified", options.lastModified);
			}
		} else {
			resp.setHeader("Cache-Control", "max-age=0"); // For support IE 6~8 ...
		}

		if (options.supportRange)
			resp.setHeader("Accept-Ranges", "bytes");

        if (!options.inline || forceDisposition) {
			String userAgent = req.getHeader("User-Agent").toLowerCase();
			UserAgentUtils.UserAgentInfo uaInfo = UserAgentUtils.parse(userAgent);
			String disposition = options.inline ? "inline" : "attachment";
			if (uaInfo.browser == UserAgentUtils.BrowserType.FIREFOX) {
				resp.addHeader("Content-Disposition", disposition + "; filename*=\"utf-8''"
					+ options.fileName + "\"");
			} else {
				resp.addHeader("Content-Disposition", disposition + "; filename=\""
					+ options.fileName + "\"");
			}
		}

		resp.addHeader("Transfer-Encoding", "identity");
		String connection = req.getHeader("Connection");
        if (connection != null) {
			resp.addHeader("Connection", connection);
		}
    }

    public static void sendJsonObject(HttpServletResponse response, Integer status, Object obj, boolean compress) {
        response.setStatus(status);
        sendJsonObject(response, obj, compress);
    }

    public static void sendJsonObject(HttpServletResponse response, Integer status, Object obj) {
        response.setStatus(status);
        sendJsonObject(response, obj, false);
    }

    public static void sendJsonObject(HttpServletResponse response, Object obj) {
        sendJsonObject(response, obj, false);
    }

    public static void sendJsonObject(HttpServletResponse response, Object obj, boolean compress) {
        try {
			response.setContentType("application/json");
			response.setCharacterEncoding("utf-8");
			response.setHeader("Pragma", "no-cache");
			response.setHeader("Cache-Control", "no-store");
			response.setDateHeader("Expires", 0);
			if (compress) {
				response.addHeader("Content-Encoding", "gzip");
				response.addHeader("Transfer-Encoding", "chunked");
				try (GZIPOutputStream gzipStream = new GZIPOutputStream(response.getOutputStream(), true)) {
					try (OutputStreamWriter writer = new OutputStreamWriter(gzipStream, "utf-8")) {
						Serializer.toJson(obj, writer);
						writer.flush();
					}
					gzipStream.finish();
					gzipStream.flush();
				}
			} else {
				try (PrintWriter writer = response.getWriter()) {
					Serializer.toJson(obj, writer);
					writer.flush();
				}
			}
		} catch (Exception ex) {
			logger.log(Level.SEVERE, "Send message to client failed!", ex);
		}
    }

    public static void setCookie(HttpServletResponse response, String name, String value, String path, String domain, Integer maxAge, boolean httpOnly, boolean secureOnly) {
        try {
            if (name == null || value == null)
                return;
            Cookie cookie = new Cookie(name, value);
            if (maxAge != null)
                cookie.setMaxAge(maxAge);
            if (domain != null && !domain.isEmpty())
                cookie.setDomain(domain);
            if (path != null && !path.isEmpty())
                cookie.setPath(path);
            cookie.setHttpOnly(httpOnly);
            cookie.setSecure(secureOnly);
            cookie.setVersion(1);
            if (response != null)
                response.addCookie(cookie);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static void setCookie(HttpServletResponse response, String name, String value, String path, String domain, Integer maxAge)  {
        setCookie(response, name, value, path, domain, maxAge, true, false);
    }

    public static void setCookie(HttpServletResponse response, String name, String value, String path)  {
        setCookie(response, name, value, path, null, null, true, false);
    }

    public static void setCookie(HttpServletResponse response, String name, String value, Integer maxAge)  {
        setCookie(response, name, value, null, null, maxAge, true, false);
    }

    public static void setCookie(HttpServletResponse response, String name, String value)  {
        setCookie(response, name, value, null, null, null, true, false);
    }

    public static String getCookie(HttpServletRequest request, String name)  {
        Cookie[] cookies = request.getCookies();
        if (cookies == null) {
            return null;
        } else {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(name)) {
                    return cookie.getValue();
                }
            }
            return null;
        }
    }

    public static void setLanguageCookie(WebApplication application, HttpServletRequest request, HttpServletResponse response, String lang, String path) {
        if (path == null) {
            String root = application.getRootPath(request);
            if (root == null || root.isEmpty()) {
                path = "/";
            } else
                path = root;
        }
        Integer maxAge = application.getSetting().sessionDays * 60 * 60 * 24;
        setCookie(response, "tt-lang", lang, path, application.getSetting().sessionDomain, maxAge, false, false);
    }

    public static void setLanguageCookie(WebApplication application, HttpServletRequest request, HttpServletResponse response, String lang) {
        setLanguageCookie(application, request, response, lang, null);
    }

    public static void download(HttpServletRequest req,
                                HttpServletResponse resp,
                                byte[] content,
                                String fileName,
								boolean inline) throws IOException {
		DownloadOptions options = new DownloadOptions();
		options.fileName = fileName;
		options.inline = inline;
		options.fileLength = (long)content.length;
        try (ByteArrayInputStream inputStream = new ByteArrayInputStream(content)) {
            download(req, resp, inputStream, options);
        }
    }


	public static void download(HttpServletRequest req,
								HttpServletResponse resp,
								File file) throws IOException {
		if (!file.isFile()) {
			ServletUtils.sendText(resp, HttpServletResponse.SC_NOT_FOUND, "File Not Found!");
			return;
		}
		DownloadOptions options = new DownloadOptions();
		options.fileName = file.getName();
		options.fileLength = file.length();
		try (FileInputStream inputStream = new FileInputStream(file)) {
			download(req, resp, inputStream, options);
		}
	}

	public static void download(HttpServletRequest req,
                                HttpServletResponse resp,
                                File file, boolean inline) throws IOException {
		if (!file.isFile()) {
			ServletUtils.sendText(resp, HttpServletResponse.SC_NOT_FOUND, "File Not Found!");
			return;
		}
		DownloadOptions options = new DownloadOptions();
		options.inline = inline;
		options.fileName = file.getName();
		options.fileLength = file.length();
		try (FileInputStream inputStream = new FileInputStream(file)) {
			download(req, resp, inputStream, options);
		}
    }

	public static void download(HttpServletRequest req,
								HttpServletResponse resp,
								File file,
								String fileName) throws IOException {
		if (!file.isFile()) {
			ServletUtils.sendText(resp, HttpServletResponse.SC_NOT_FOUND, "File Not Found!");
			return;
		}
		DownloadOptions options = new DownloadOptions();
		options.fileName = fileName;
		options.inline = false;
		options.fileLength = file.length();
		try (FileInputStream inputStream = new FileInputStream(file)) {
			download(req, resp, inputStream, options);
		}
	}

	public static void download(HttpServletRequest req,
								HttpServletResponse resp,
								File file,
								String fileName,
								String mimeType) throws IOException {
		if (!file.isFile()) {
			ServletUtils.sendText(resp, HttpServletResponse.SC_NOT_FOUND, "File Not Found!");
			return;
		}
		DownloadOptions options = new DownloadOptions();
		options.fileName = fileName;
		options.mimeType = mimeType;
		options.fileLength = file.length();
		try (FileInputStream inputStream = new FileInputStream(file)) {
			download(req, resp, inputStream, options);
		}
	}

    public static void download(HttpServletRequest req,
                                HttpServletResponse resp,
                                File file,
								DownloadOptions options) throws IOException {
		if (!file.isFile()) {
			ServletUtils.sendText(resp, HttpServletResponse.SC_NOT_FOUND, "File Not Found!");
			return;
		}
        try (FileInputStream inputStream = new FileInputStream(file)) {
            download(req, resp, inputStream, options);
        }
    }

    private static Pattern RANGE_PATTERN = Pattern.compile("^(?i)\\s*bytes\\s*=\\s*(\\d*)\\s*-\\s*(\\d*)$");
	private static Pattern ETAG_PATTERN = Pattern.compile("^(?i)(?:W\\s*/\\s*)?\"(.+)\"|(.+)$");

	private static String getETag(String eTag) {
		Matcher matcher = ETAG_PATTERN.matcher(eTag);
		if (matcher == null || !matcher.find())
			return null;
		String v = matcher.group(1);
		if (v == null)
			v = matcher.group(2);
		return v;
	}

	private static boolean isValid(String v) {
		if (v == null || v.isEmpty())
			return false;
		else
			return true;
	}

	private static String getErrorMessage(Throwable e) {
		if (e == null)
			return "Unknown error";
		while (e.getCause() != null) {
			e = e.getCause();
		}
		if (e.getMessage() != null)
			return e.getMessage();
		else
			return e.getClass().getName();
	}

    public static void download(HttpServletRequest req,
                                HttpServletResponse resp,
                                InputStream inputStream,
								DownloadOptions options) throws IOException {
        if (options == null)
			options = new DownloadOptions();
		sendDownloadHeader(req, resp, options);

		String range = req.getHeader("Range");
		if (range == null || !options.supportRange || options.fileLength == null) {
			String ifNoneMatch = req.getHeader("If-None-Match");
			if (ifNoneMatch != null && options.eTag != null) {
				if (options.eTag.trim().equals(getETag(ifNoneMatch).trim())) {
					sendText(resp, 304, "Not Modified!");
					return;
				}
			}

			Long ifModifiedSince = req.getDateHeader("If-Modified-Since");
			if (ifModifiedSince != null && options.lastModified != null) {
				if (ifModifiedSince == options.lastModified) {
					sendText(resp, 304, "Not Modified!");
					return;
				}
			}

			if (options.fileLength != null) {
				resp.setContentLengthLong(options.fileLength);
			}

			long totalSent = 0;
			try (OutputStream outputStream = resp.getOutputStream()) {
				int length;
				byte[] buffer = new byte[8192];
				while ((length = inputStream.read(buffer)) > 0) {
					outputStream.write(buffer, 0, length);
					totalSent += length;
					if (options.fileLength != null)
						outputStream.flush();
				}
			} catch (IOException e) {
				System.out.println("Complete download(1): " + getErrorMessage(e));
			} finally {
				System.out.println("Should send all bytes to client(1): " +
					options.fileLength + ", actually sent: " + totalSent);
			}
		} else {
			Matcher matcher = RANGE_PATTERN.matcher(range);
			if (matcher == null || !matcher.find()) {
				sendText(resp, 416, "Range Not Satisfiable");
				return;
			}
			String sBegin = matcher.group(1);
			String sEnd = matcher.group(2);
			long begin, end;
			if (!isValid(sBegin) && !isValid(sEnd)) {
				sendText(resp, 416, "Range Not Satisfiable");
				return;
			} else if (!isValid(sBegin)) {
				begin = options.fileLength - Long.parseLong(sEnd);
				end = options.fileLength - 1;
			} else if (!isValid(sEnd)) {
				begin = Long.parseLong(sBegin);
				end = options.fileLength - 1;
			} else {
				begin = Long.parseLong(sBegin);
				end = Long.parseLong(sEnd);
			}
			if (end < begin) {
				sendText(resp, 416, "Range Not Satisfiable");
				return;
			}

			String ifRange = req.getHeader("If-Range");
			if (ifRange != null) {
				if (options.eTag == null || !ifRange.trim().equals(options.eTag.trim())) {
					resp.setStatus(200);
					if (options.fileLength != null) {
						resp.setContentLengthLong(options.fileLength);
					}
					long totalSent = 0;
					try (OutputStream outputStream = resp.getOutputStream()) {
						int length;
						byte[] buffer = new byte[8192];
						while ((length = inputStream.read(buffer)) > 0) {
							outputStream.write(buffer, 0, length);
							totalSent += length;
							if (options.fileLength != null)
								outputStream.flush();
						}
					} catch (IOException e) {
						System.out.println("Complete download(2): " + getErrorMessage(e));
					} finally {
						System.out.println("Should send all bytes to client(2): " +
							options.fileLength + ", actually sent: " + totalSent);
					}
					return;
				}
			}

			long totalSent = 0;
			long rangeSize = end - begin + 1;
			try (ServletOutputStream outputStream = resp.getOutputStream()) {
				resp.setHeader("Content-Range", "bytes " + begin + "-" + end + "/" + options.fileLength);
				resp.setStatus(206);
				resp.setContentLengthLong(rangeSize);
				inputStream.skip(begin);
				int length;
				byte[] buffer = new byte[8192];
				while ((length = inputStream.read(buffer, 0,
					(int)(rangeSize > 4096 ? 4096 : rangeSize))) > 0) {
					rangeSize -= length;
					outputStream.write(buffer, 0, length);
					totalSent += length;
					outputStream.flush();
				}
			} catch (IOException e) {
				System.out.println("Partial download: " + getErrorMessage(e));
			} finally {
				System.out.println("Send all bytes to client: begin: " + begin + ", end: " + end +
					", should send: " + (end - begin + 1) + ", real sent: " + totalSent);
			}

		}
    }

	public static void download(HttpServletRequest request,
							 HttpServletResponse response,
							 FileStoreService fileStore,
							 String fileId)
		throws IOException {
		download(request, response, fileStore, fileId, null);
	}

	public static void download(HttpServletRequest request,
							 	HttpServletResponse response,
							 	FileStoreService fileStore,
							 	String fileId,
								DownloadOptions options)
		throws IOException {
		response.setCharacterEncoding("utf-8");
		FileStoreService.FileInfo info = fileStore.getFileInfo(fileId);
		if (info == null) {
			ServletUtils.sendText(response, HttpServletResponse.SC_NOT_FOUND, "File Not Found!");
			return;
		}
		File dataFile = fileStore.getDataFile(info);
		if (!dataFile.isFile()) {
			ServletUtils.sendText(response, HttpServletResponse.SC_NOT_FOUND, "File Not Found!");
			return;
		}
		if (options == null) {
			options = new DownloadOptions();
		}
		if (options.fileName == null)
			options.fileName = info.fileName;
		if (options.mimeType == null)
			options.mimeType = info.mimeType;
		options.fileLength = info.length;
		if (options.lastModified == null)
			options.lastModified = info.modifyTime;
		if (options.eTag == null)
			options.eTag = info.hash;
		download(request, response, dataFile, options);
	}

	public static List<FileStoreService.FileInfo> saveUploadFiles(HttpServletRequest request, FileStoreService fileStore) throws ServletException, IOException, FileUploadException, NoSuchAlgorithmException {
		return saveUploadFiles(request, fileStore, null);
	}

	private static String toFileName(String name, String contentType) {
		if (name == null) {
			if (contentType == null) {
				return "file.data";
			} else {
				return "file." + MimeUtils.getFileSuffix(contentType);
			}
		}
		String fileName = name;
		if (fileName.lastIndexOf("\\") != -1) {
			fileName = fileName.substring(fileName.lastIndexOf("\\") + 1);
		}
		if (fileName.lastIndexOf("/") != -1) {
			fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
		}
		return fileName;
	}


	public static List<FileStoreService.FileInfo> saveUploadFiles(
		HttpServletRequest request, FileStoreService fileStore, UploadOptions options)
		throws IOException, FileUploadException, NoSuchAlgorithmException {

		if (options == null) {
			options = new UploadOptions();
		}
		if (options.maxUploadFileSize <= 0) {
			options.maxUploadFileSize = Long.MAX_VALUE;
		}
		Pattern suffixPattern = null;
		if (!Strings.isNullOrEmpty(options.allowSuffix)) {
			suffixPattern = Pattern.compile("(?i).+\\.(" + options.allowSuffix + ")");
		}
		Pattern fileNamePattern = null;
		if (!Strings.isNullOrEmpty(options.fileNamePattern)) {
			fileNamePattern = Pattern.compile(options.fileNamePattern);
		}

		List<FileStoreService.FileInfo> uploadList = new LinkedList<>();
		request.setCharacterEncoding("utf-8");
		ServletFileUpload upload = new ServletFileUpload();
		upload.setHeaderEncoding("UTF-8");
		if (!ServletFileUpload.isMultipartContent(request)) {
			return uploadList;
		}
		upload.setSizeMax(options.maxUploadFileSize);
		FileItemIterator iterator = upload.getItemIterator(request);
		while (iterator.hasNext()) {
			FileItemStream item = iterator.next();
			try (InputStream stream = item.openStream()) {
				if (!item.isFormField()) {
					String fileName = toFileName(item.getName(), item.getContentType());
					if (suffixPattern != null && !suffixPattern.matcher(fileName).matches()) {
						continue;
					}
					if (fileNamePattern != null && !fileNamePattern.matcher(fileName).matches()) {
						continue;
					}
					FileStoreService.FileInfo info = fileStore.store(stream, fileName);
					uploadList.add(info);
				}
			}
		}
		return uploadList;
	}

    private final static Pattern filenamePattern = Pattern.compile("filename\\s*\\*?\\s*=\\s*(?:\"(?:utf-8'')?([^\"]+)\"|\\s*(?:utf-8'')?([^;\\s]+))");
    public static String getFilenameFromContentDisposition(String contentDisposition) {
        if (contentDisposition == null)
            return null;
        Matcher matcher = filenamePattern.matcher(contentDisposition);
        if (matcher.find()) {
            String fileName = matcher.group(1);
            if (fileName == null)
                fileName = matcher.group(2);
            try {
                return URLDecoder.decode(fileName, "utf-8");
            } catch (Exception e) {
                return fileName;
            }
        } else
            return null;
    }


    private final static Pattern urlFilenamePattern = Pattern.compile("(?:/([^/]+))");
    public static String getFilenameFromUrl(String url) {
        if (url == null)
            return null;
        Matcher matcher = urlFilenamePattern.matcher(url);
        String lastPath = null;
        while (matcher.find()) {
            String path = matcher.group(1);
            if (path != null)
                lastPath = path;
        }
        if (lastPath != null) {
            String[] parts = lastPath.split("[?#]");
            String fileName = parts[0];
            if (fileName == null)
                return null;
            fileName = fileName.replaceAll("[\\r\\n]", "");
            try {
                return URLDecoder.decode(fileName, "utf-8");
            } catch (Exception e) {
                return fileName;
            }
        } else
            return null;
    }

	public static String getRealClientIp(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if(ip == null || ip.length() == 0) {
			ip = request.getHeader("x-real-ip");
			if (ip == null || ip.length() == 0)
				return request.getRemoteAddr();
			else
				return ip;
		} else {
			String[] list = ip.split(",");
			for (String item: list) {
				if (item == null || item.trim().length() == 0 || item.equals("unknown"))
					continue;
				else
					return item.trim();
			}
			return request.getRemoteAddr();
		}
	}

	public static String getLangSetting(WebApplication app, HttpServletRequest request) {
    	String lang = app.getSetting().lang;
		if (request.getCookies() != null) {
			for (Cookie cookie : request.getCookies()) {
				String name = cookie.getName();
				if (name != null && name.equals("tt-lang")) {
					lang = cookie.getValue();
					break;
				}
			}
		}
		return lang;
	}
}
